#include "stdafx.h"

#include "ParticleSystem.h"

ParticleSystem::ParticleSystem() : bufSize(1<<21), bufIdx(0), freePtr(0), activePtr(0) {

	glBufs[0].vao = 0;
	glBufs[0].vbo = 0;
	glBufs[1].vao = 0;
	glBufs[1].vbo = 0;

	renderShader = new Shader();
	renderShader->addStage("./graphics/shaders/particleDraw.vert", "", GL_VERTEX_SHADER);
	renderShader->addStage("./graphics/shaders/particleDraw.frag", "", GL_FRAGMENT_SHADER);
	renderShader->install();

	simShader = new Shader();
	simShader->addStage("./graphics/shaders/particleSim.vert", "", GL_VERTEX_SHADER);
	simShader->addVarying("vPos"); simShader->addVarying("vVel"); simShader->addVarying("vColor"); simShader->addVarying("vProps");
	simShader->install();

	for ( int i = 0; i < 2; ++i){
		glGenVertexArrays(1, &glBufs[i].vao);
		glGenBuffers(1, &glBufs[i].vbo);
		glBindVertexArray(glBufs[i].vao);
		glBindBuffer(GL_ARRAY_BUFFER, glBufs[i].vbo);
		glBufferData(GL_ARRAY_BUFFER, bufSize * sizeof(ParticleVertex), nullptr, GL_STATIC_DRAW);

		unsigned int offset = 0;
		glVertexAttribPointer(semantic::attr::PARTICLE_POSITION, 4, GL_FLOAT, GL_FALSE, sizeof(ParticleVertex), BUFFER_OFFSET(offset));
		offset += sizeof(glm::vec4);
		glVertexAttribPointer(semantic::attr::PARTICLE_VEL, 3, GL_FLOAT, GL_FALSE, sizeof(ParticleVertex), BUFFER_OFFSET(offset));
		offset += sizeof(glm::vec3);
		glVertexAttribPointer(semantic::attr::PARTICLE_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof(ParticleVertex), BUFFER_OFFSET(offset));
		offset += sizeof(glm::vec4);
		glVertexAttribPointer(semantic::attr::PARTICLE_PROP, 4, GL_FLOAT, GL_FALSE, sizeof(ParticleVertex), BUFFER_OFFSET(offset));

		glEnableVertexAttribArray(semantic::attr::PARTICLE_POSITION);
		glEnableVertexAttribArray(semantic::attr::PARTICLE_VEL);
		glEnableVertexAttribArray(semantic::attr::PARTICLE_COLOR);
		glEnableVertexAttribArray(semantic::attr::PARTICLE_PROP);

		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindVertexArray(0);
	}

	::flushGLError("ParticleSystem::ParticleSystem()");

	
}

void ParticleSystem::AddParticles(unsigned int count, float ttl, ParticleEvo evo, const glm::vec3 & pos, float posRand, float size, const glm::vec3 & vel, float velRand, std::vector<glm::vec4> & colors) {

	std::vector<ParticleVertex> vertices(count);

	glm::vec4 pos4 = glm::vec4(pos.x, pos.y, pos.z, 1.0f);
	for	( unsigned int i = 0; i < count; ++i ) {
		int colorIdx = rand() % colors.size();
		vertices[i] = ParticleVertex(
			pos4 + glm::vec4(posRand*randNormNegPos(), posRand*randNormNegPos(), posRand*randNormNegPos(), 0.0f),
			//vel + glm::vec3(velRand*randNormNegPos(), velRand*randNormNegPos(), velRand*randNormNegPos()), 
			vel + velRand * randNormPos() * glm::normalize(glm::vec3(randNormNegPos(), randNormNegPos(), randNormNegPos())), 
			colors[colorIdx],
			glm::vec4(ttl, 0.0f/*used for acc time*/, size, static_cast<float>(evo)));
	}

	glBindBuffer(GL_ARRAY_BUFFER, getBufIn().vbo);
	{
		if (freePtr + count > bufSize) {
			// wraps: |           after->|                       before->|          |
			unsigned int after = (freePtr + count) % bufSize;
			unsigned int before = count - after;
			glBufferSubData(GL_ARRAY_BUFFER, sizeof(ParticleVertex) * freePtr, sizeof(ParticleVertex) * before, &vertices[0]);
			glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(ParticleVertex) * after, &vertices[before]);
		} 
		else {
			glBufferSubData(GL_ARRAY_BUFFER, sizeof(ParticleVertex) * freePtr, sizeof(ParticleVertex) * vertices.size(), &vertices[0]);
		}
	} glBindBuffer(GL_ARRAY_BUFFER, 0);

	freePtr += vertices.size();
	freePtr %= bufSize;
	// register particles so we can bump activePtr when they expire 
	particleStoreList.push_back(ParticleStore(ttl, count));

	::flushGLError("ParticleSystem::AddParticles()");
}

void ParticleSystem::BlueExplosion(const glm::vec3 & pos, float size) {
	static std::vector<glm::vec4> blueExplColors;
	if (blueExplColors.empty()) {
		blueExplColors.push_back(glm::vec4(0.0f, 1.0f, 1.0f, 0.145f));
		blueExplColors.push_back(glm::vec4(1.0f, 0.0f, 0.0f, 0.555f));
		blueExplColors.push_back(glm::vec4(1.0f, 1.0f, 1.0f, 0.285f));
	}

	//colors.push_back(glm::vec4(1.0f, 0.0f, 0.0f, 1.0f));
	AddParticles(static_cast<size_t>(size*40.0f), 0.75f, ParticleSystem::out_quadratic, pos, 0.0f, 3.0f,  
		glm::vec3(0.0f, 0.0f, 0.0f), size*5.0f, blueExplColors);
}


void ParticleSystem::UpdateAndDraw(float dt, const glm::mat4 & viewMatrix) {

#if 1
	static float tCounter = 26.0f;
	static int meh = 0;
	tCounter += dt;
	if(tCounter >= 3.5f) {
		tCounter = 0.0f;
		for ( int i = 0; i < 1; ++i ) {
			std::vector<glm::vec4> colors;

#if 0 
			meh++;
			if (meh % 2 == 0 ) {
				colors.push_back(glm::vec4(0.8f, 0.3f, 0.6f, 0.1f));colors.push_back(glm::vec4(0.2f, 0.7f, 9.0f, 0.1f));colors.push_back(glm::vec4(0.9f, 0.5f, 0.0f, 0.1f));
				colors.push_back(glm::vec4(0.0f, 0.3f, 1.0f, 0.1f));
			} else {
				colors.push_back(glm::vec4(0.8f, 0.2f, 0.2f, 0.1f));colors.push_back(glm::vec4(0.7f, 0.5f, 0.2f, 0.1f));colors.push_back(glm::vec4(0.6f, 0.9f, 0.3f, 0.1f));
				colors.push_back(glm::vec4(0.3f, 0.0f, 0.0f, 0.1f));
			}
#else 
			
#endif 

			/*colors.push_back(glm::vec4(0.0f, 1.0f, 0.0f, 0.145f));
			colors.push_back(glm::vec4(1.0f, 0.0f, 0.0f, 0.055f));
			colors.push_back(glm::vec4(0.0f, 0.0f, 1.0f, 0.185f));*/
			//colors.push_back(glm::vec4(1.0f, 0.0f, 0.0f, 1.0f));
			/*AddParticles(50, 0.5f, glm::vec3(128.0f, 30.0f, 128.0f) + glm::vec3(randNormNegPos()*50.0f, randNormNegPos(), randNormNegPos()*50.0f), 44.5f,  
				0.0f, 1.0f*glm::normalize(glm::vec3(randNormNegPos(), 5.0f, randNormNegPos())), 25.0f, colors);*/

			colors.push_back(glm::vec4(0.0f, 0.0f, 1.0f, 0.125f));
			colors.push_back(glm::vec4(1.0f, 1.0f, 0.0f, 0.15f));
			colors.push_back(glm::vec4(0.0f, 1.0f, 0.0f, 0.15f));

			/*AddParticles(50, 0.5f, glm::vec3(128.0f, 30.0f, 128.0f) + glm::vec3(randNormNegPos()*50.0f, randNormNegPos(), randNormNegPos()*50.0f), 44.5f,  
				0.0f, 1.0f*glm::normalize(glm::vec3(randNormNegPos(), 5.0f, randNormNegPos())), 25.0f, colors);*/

			/*AddParticles(1, 10.0f, ParticleSystem::inout_quadratic, glm::vec3(128.0f, 30.0f, 128.0f), 5.0f,
				44.4f, glm::vec3(0.0f, 15.0f, 0.0f), 5.0f, colors);

			AddParticles(1, 10.0f, ParticleSystem::inout_linear, glm::vec3(128.0f + 50.0f, 30.0f, 128.0f), 5.0f,
				44.4f, glm::vec3(0.0f, 15.0f, 0.0f), 5.0f, colors);*/

			/*AddParticles(1, 2.0f, ParticleSystem::inout_linear, glm::vec3(128.0f + 100.0f, 30.0f, 128.0f), 5.0f,
				44.4f, glm::vec3(0.0f, 15.0f, 0.0f), 5.0f, colors);

			AddParticles(1, 2.0f, ParticleSystem::inout_quadratic, glm::vec3(128.0f + 200.0f, 30.0f, 128.0f), 5.0f,
				44.4f, glm::vec3(0.0f, 15.0f, 0.0f), 5.0f, colors);*/

			/*AddParticles(1, 5.0f, glm::vec3(128.0f, 30.0f, 128.0f), 44.4f,
				0.0f, glm::vec3(0.0f, 15.0f, 0.0f), 10.0f, colors);*/

			/*AddParticles(500, 0.5f, glm::vec3(128.0f, 30.0f, 128.0f) + glm::vec3(randNormNegPos()*50.0f, randNormNegPos(), randNormNegPos()*50.0f), 4.5f,  
				0.0f, 1.0f*glm::normalize(glm::vec3(randNormNegPos(), 5.0f, randNormNegPos())), 25.0f, colors);*/

			//AddParticles(5, 26.5f, glm::vec3(128.0f, 10.0f, 128.0f) + glm::vec3(randNormNegPos()*50.0f, randNormNegPos(), randNormNegPos()*50.0f), 0.0f, glm::vec3(0.0f, 0.0f, 0.0f), 5.0f, colors);
			//AddParticles(1, 50.0f, glm::vec3(128.0f, 10.0f, 128.0f) + glm::vec3(randNormNegPos()*50.0f, randNormNegPos(), randNormNegPos()*50.0f), 5.0f, glm::vec3(0.0f, 0.0f, 0.0f), 0.0f, colors);
			/*
			colors.push_back(glm::vec4(1.0f, 0.0f, 0.0f, 0.1f));
			AddParticles(5, 25.5f, glm::vec3(128.0f, 13.0f, 128.0f), 0.0f, glm::vec3(0.0f, 0.0f, 0.0f), 0.0f, colors);
			colors.clear();
			colors.push_back(glm::vec4(0.0f, 1.0f, 0.0f, 0.1f));
			AddParticles(5, 25.5f, glm::vec3(128.0f, 16.0f, 128.0f), 0.0f, glm::vec3(0.0f, 0.0f, 0.0f), 0.0f, colors);
			colors.clear();
			colors.push_back(glm::vec4(1.0f, 0.0f, 0.0f, 0.1f));
			AddParticles(5, 25.5f, glm::vec3(128.0f, 19.0f, 128.0f), 0.0f, glm::vec3(0.0f, 0.0f, 0.0f), 0.0f, colors);
			colors.clear();
			colors.push_back(glm::vec4(1.0f, 1.0f, 0.0f, 0.1f));
			AddParticles(5, 25.5f, glm::vec3(128.0f, 22.0f, 128.0f), 0.0f, glm::vec3(0.0f, 0.0f, 0.0f), 0.0f, colors);
			*/
			/*
			colors.AddValue(0.0f,  glm::vec4(1, 0, 0, 1) );     // red
			colors.AddValue(0.15f, glm::vec4(1, 0, 1, 1) );     // magenta
			colors.AddValue(0.33f, glm::vec4(0, 0, 1, 1) );     // blue
			colors.AddValue(0.5f,  glm::vec4(0, 1, 1, 1) );     // cyan
			colors.AddValue(0.67f, glm::vec4(0, 1, 0, 0.75) );  // green
			colors.AddValue(0.84f, glm::vec4(1, 1, 0, 0.5) );   // yellow
			colors.AddValue(1.0f,  glm::vec4(1, 0, 0, 0) );     
			*/
		}
	}
#endif 
	if (particleStoreList.empty())
		return;

	for (auto it = particleStoreList.begin(); it != particleStoreList.end(); ++it)
	{
		(*it).ttl -= dt;
	}
	
	if ( particleStoreList.front().ttl < 0.0f ) 
	{
		activePtr += particleStoreList.front().count; // bump the active index
		activePtr %= bufSize;
		particleStoreList.pop_front(); // forget the particles 
#if 0 // debug 
		std::printf("removing particles: ");
		std::printf("ActivePtr:%i ", activePtr);
		std::printf("FreePtr:%i\n", freePtr);
#endif 
	}

	// 1. update particles using transform feedback 
	simShader->begin();
	{
		glUniform1f(simShader->getUniLoc("dt"), dt);
		glUniform3fv(simShader->getUniLoc("accl"), 1, glm::value_ptr(glm::vec3(0.0f, 0.0f /*-9.81f*/, 0.0f)));

		glEnable(GL_RASTERIZER_DISCARD);
		glBindVertexArray(getBufIn().vao); // source

		if (freePtr < activePtr) {		
			glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, getBufOut().vbo, activePtr * sizeof(ParticleVertex), (bufSize-activePtr)*sizeof(ParticleVertex)); // dest 
			glBeginTransformFeedback(GL_POINTS);
			glDrawArrays(GL_POINTS, activePtr, bufSize - activePtr);
			glEndTransformFeedback();

			if (freePtr > 0) {
				glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, getBufOut().vbo, 0, freePtr*sizeof(ParticleVertex)); // dest 
				glBeginTransformFeedback(GL_POINTS);
				glDrawArrays(GL_POINTS, 0, freePtr);
				glEndTransformFeedback();
			}
		}
		else {
			if (freePtr-activePtr > 0) {
				glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, getBufOut().vbo, activePtr * sizeof(ParticleVertex), (freePtr-activePtr)*sizeof(ParticleVertex)); // dest 
				glBeginTransformFeedback(GL_POINTS);
				glDrawArrays(GL_POINTS, activePtr, freePtr - activePtr);
				glEndTransformFeedback();
			}
		}
		glBindVertexArray(0);
		
		glDisable(GL_RASTERIZER_DISCARD);
	}
	simShader->end();

	glEnable(GL_BLEND);
	/*glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);*/
	glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
	//glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
	glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ZERO); // Additive blending
	glDepthMask(GL_FALSE);

	// 2. draw particles
	renderShader->begin();
	{
		int viewLoc = renderShader->getUniLoc("view");
		int projLoc = renderShader->getUniLoc("proj");
		int screenSizeLoc = renderShader->getUniLoc("screenSize");

		glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(viewMatrix));
		glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(Game::getPerspMatrix()));
		glUniform2f(screenSizeLoc, (GLfloat)Game::windowDim.x, (GLfloat)Game::windowDim.y);

		glBindVertexArray(getBufOut().vao);
		{
			if (freePtr < activePtr) {
				glDrawArrays(GL_POINTS, activePtr, bufSize - activePtr);
				if (freePtr>0)
					glDrawArrays(GL_POINTS, 0, freePtr);
			}
			else {
				if (freePtr-activePtr > 0)
					glDrawArrays(GL_POINTS, activePtr, freePtr - activePtr);
			}
		} glBindVertexArray(0);
	}
	renderShader->end();

	glDepthMask(GL_TRUE);
	glDisable(GL_BLEND);

	advanceBuf();
	::flushGLError("ParticleSystem::UpdateAndDraw()");

#if 0 // debug 
	if (freePtr >= activePtr)
		std::printf("Number of particles: %i\n", freePtr - activePtr);
	else 
		std::printf("Number of particles: %i\n", bufSize - activePtr + freePtr);
#endif 
}